package com.freecloud.bitorrent.domain.server;

import com.freecloud.bitorrent.common.util.bencode.BencodingUtils;
import com.freecloud.bitorrent.constant.BitTorrentConstant;
import com.freecloud.bitorrent.constant.LocalNodeConstant;
import com.freecloud.bitorrent.demo.bo.BitTorrentMessage;
import com.freecloud.bitorrent.demo.bo.FindNodeRequest;
import com.freecloud.bitorrent.demo.bo.RequestMessage;
import com.freecloud.bitorrent.domain.server.support.DhtCommandFactory;
import com.freecloud.bitorrent.enums.MessageTypeEnum;
import com.freecloud.bitorrent.enums.QueryEnum;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.socket.DatagramPacket;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.net.InetSocketAddress;
import java.util.Map;

/**
 * @Author: maomao
 * @Date: 2022/7/15 6:14 PM
 */
@Slf4j
@Component
public class DHTServer {

    private final Bootstrap dhtServerBootstrap;
    private final InetSocketAddress udpSocketAddress;
    private final DhtCommandFactory dhtCommandFactory;

    private ChannelFuture serverChannelFuture;

    public DHTServer(Bootstrap dhtServerBootstrap, InetSocketAddress udpSocketAddress,
        DhtCommandFactory dhtCommandFactory) {
        this.dhtServerBootstrap = dhtServerBootstrap;
        this.udpSocketAddress = udpSocketAddress;
        this.dhtCommandFactory = dhtCommandFactory;
    }

    /**
     * 启动DHT服务
     *
     * @throws Exception
     */
    @PostConstruct
    public void start() throws Exception {
        log.info("Starting dht server at " + udpSocketAddress);
        serverChannelFuture = dhtServerBootstrap.bind(udpSocketAddress).sync();
        serverChannelFuture.channel().closeFuture();
    }

    /**
     * 发送 KRPC 协议数据报文
     *
     * @param message
     */
    public void sendKRPC(BitTorrentMessage message, InetSocketAddress address) {
        log.info("发送 KRPC 协议数据报文 : {}", message);
        DatagramPacket sendMessage = null;
        if (message instanceof RequestMessage) {
            Map<String, Object> encodeMap = dhtCommandFactory.getDhtCommandEncode(((RequestMessage)message).getQuery())
                .encode((RequestMessage)message);
            log.info("requestMessage: {}", encodeMap);
            byte[] buff = BencodingUtils.encode(encodeMap);
            sendMessage = new DatagramPacket(Unpooled.copiedBuffer(buff), address);
        }
        serverChannelFuture.channel().writeAndFlush(sendMessage);
    }

    public void joinDHT() {
        for (InetSocketAddress addr : BitTorrentConstant.BOOTSTRAP_NODES) {
            log.debug("加入DHT网络：addr: {} self_nodeId:{}", addr, LocalNodeConstant.ip);
            findNode(addr, null, LocalNodeConstant.ip);
        }
    }

    private void findNode(InetSocketAddress address, String nid, String targetNodeId) {
        FindNodeRequest findNodeRequest = new FindNodeRequest(targetNodeId);
        findNodeRequest.setId(LocalNodeConstant.ip);
        RequestMessage requestMessage = new RequestMessage(QueryEnum.FindNode, findNodeRequest);
        requestMessage.setTransactionId(QueryEnum.FindNode.getValue());
        requestMessage.setMessageType(MessageTypeEnum.Query);
        sendKRPC(requestMessage, address);
    }
}
